using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using System.ComponentModel;
using System.CodeDom.Compiler;

namespace Clarius.PresentationModel.Design
{
	internal class Generator
	{
		static readonly CodeTypeReference ISupportInitializeRef = new CodeTypeReference(typeof(ISupportInitialize).Name);
		static readonly CodeTypeReference ISupportInitializeNotificationRef = new CodeTypeReference(typeof(ISupportInitializeNotification).Name);
		static readonly CodeTypeReference IChangeTrackingRef = new CodeTypeReference(typeof(IChangeTracking).Name);
		static readonly CodeTypeReference INotifyPropertyChangedRef = new CodeTypeReference(typeof(INotifyPropertyChanged).Name);
		static readonly CodeAttributeDeclaration EditorBrowsableNever = new CodeAttributeDeclaration(
			new CodeTypeReference(typeof(EditorBrowsableAttribute).Name),
			new CodeAttributeArgument(
				new CodeFieldReferenceExpression(
					new CodeTypeReferenceExpression(typeof(EditorBrowsableState).Name),
					"Never"
				)
			)
		);

		bool isCSharp;
		bool isDerivedModel;
		bool isSealed;
		CodeTypeDeclaration type;
		List<Property> publicProperties;

		public Generator(CodeTypeDeclaration type, List<Property> publicProperties, 
			bool isCSharp, bool isDerivedModel, bool isSealed)
		{
			this.type = type;
			this.publicProperties = publicProperties;
			this.isCSharp = isCSharp;
			this.isDerivedModel = isDerivedModel;
			this.isSealed = isSealed;
		}

		public void GenerateImplementation()
		{
			type.CustomAttributes.Add(new CodeAttributeDeclaration(
				new CodeTypeReference(typeof(GeneratedCodeAttribute)),
				new CodeAttributeArgument(new CodePrimitiveExpression(ThisAssembly.Title)),
				new CodeAttributeArgument(new CodePrimitiveExpression(ThisAssembly.Version))
			));

			if (isDerivedModel)
			{
				AddIChangeTrackingOverride();
				AddISupportInitializeOverride();
				AddISupportInitializeNotificationOverride();
			}
			else
			{
				AddISupportInitialize();
				AddISupportInitializeNotification();
				AddIChangeTracking();
				AddINotifyPropertyChanged();
				AddValidation();
			}

			foreach (Property prop in publicProperties)
			{
				AddPropertyChanged(prop.Name);
			}
		}

		private void AddISupportInitializeOverride()
		{
			if (publicProperties.Count > 0)
			{
				///// <summary>
				///// Initializes the nested properties that support <see cref="ISupportInitialize"/>.
				///// </summary>
				//[EditorBrowsable(EditorBrowsableState.Never)]
				//protected override void InitializeNested()
				//{
				//   base.InitializeNested();
				//   ISupportInitialize initializable;

				//   initializable = (ISupportInitialize)nested;
				//   initializable.BeginInit();
				//   initializable.EndInit();
				//}
				CodeMemberMethod initializeNested = new CodeMemberMethod();
				initializeNested.Name = "InitializeNested";
				initializeNested.Comments.Add(new CodeCommentStatement(
					"<summary>Initializes the nested properties that support <see cref='ISupportInitialize'/>.</summary>", true));
				initializeNested.CustomAttributes.Add(EditorBrowsableNever);
				initializeNested.Attributes = MemberAttributes.Family | MemberAttributes.Override;
				initializeNested.Statements.Add(
					new CodeMethodInvokeExpression(
						new CodeBaseReferenceExpression(),
						"InitializeNested"
					)
				);
				initializeNested.Statements.Add(
					new CodeVariableDeclarationStatement(ISupportInitializeRef, "initializable"));
				CodeExpression initializable = new CodeVariableReferenceExpression("initializable");
				foreach (Property prop in publicProperties)
				{
					if (prop.ShouldCast)
					{
						initializeNested.Statements.Add(
							new CodeAssignStatement(
								initializable,
								CastProperty(prop.Name, prop.ImplementsISupportInitialize, ISupportInitializeRef)
							)
						);
						initializeNested.Statements.Add(
							new CodeConditionStatement(
								new CodeBinaryOperatorExpression(
									initializable,
									CodeBinaryOperatorType.IdentityInequality,
									new CodePrimitiveExpression(null)
								),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"BeginInit")),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"EndInit"))
							)
						);
					}
				}
				type.Members.Add(initializeNested);
			}
		}

		private void AddISupportInitializeNotificationOverride()
		{
			///// <summary>
			///// Gets whether the object properties that support 
			///// <see cref="ISupportInitializeNotification"/> are initialized.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected override bool AreNestedInitialized
			//{
			//   get
			//   {
			//      return base.AreNestedInitialized && IsValueInitialized(nested as ISupportInitializeNotification);
			//   }
			//}
			CodeMemberProperty nestedInitialized = new CodeMemberProperty();
			nestedInitialized.Name = "AreNestedInitialized";
			nestedInitialized.Comments.Add(new CodeCommentStatement(
				" <summary>Gets whether the object properties that support <see cref='ISupportInitializeNotification'/> are initialized.</summary>", true));
			nestedInitialized.CustomAttributes.Add(EditorBrowsableNever);
			nestedInitialized.Attributes = MemberAttributes.Family | MemberAttributes.Override;
			nestedInitialized.Type = new CodeTypeReference(typeof(bool));

			CodeExpression retExpr = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), nestedInitialized.Name);
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsISupportInitializeNotification,
						ISupportInitializeNotificationRef);

					retExpr = new CodeBinaryOperatorExpression(
						retExpr,
						CodeBinaryOperatorType.BooleanAnd,
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"IsValueInitialized",
							cast
						)
					);
				}
			}
			nestedInitialized.GetStatements.Add(new CodeMethodReturnStatement(retExpr));
			type.Members.Add(nestedInitialized);
		}

		private void AddIChangeTrackingOverride()
		{
			///// <summary>
			///// Gets whether the object properties that support 
			///// <see cref="IChangeTracking"/> report changes.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected override bool HasNestedChanges
			//{
			//   get
			//   {
			//      return base.HasNestedChanges || IsValueChanged(nested as IChangeTracking);
			//   }
			//}
			CodeMemberProperty hasNestedChanges = new CodeMemberProperty();
			hasNestedChanges.Name = "HasNestedChanges";
			hasNestedChanges.Comments.Add(new CodeCommentStatement(
				"<summary>Gets whether the object properties that support <see cref='IChangeTracking'/> report changes.</summary>", true));
			hasNestedChanges.CustomAttributes.Add(EditorBrowsableNever);
			hasNestedChanges.Attributes = MemberAttributes.Family | MemberAttributes.Override;
			hasNestedChanges.Type = new CodeTypeReference(typeof(bool));

			CodeExpression retExpr = new CodePropertyReferenceExpression(new CodeBaseReferenceExpression(), hasNestedChanges.Name);
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsIChangeTracking,
						IChangeTrackingRef);

					retExpr = new CodeBinaryOperatorExpression(
						retExpr,
						CodeBinaryOperatorType.BooleanOr,
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"IsValueChanged",
							cast
						)
					);
				}
			}
			hasNestedChanges.GetStatements.Add(new CodeMethodReturnStatement(retExpr));
			type.Members.Add(hasNestedChanges);

		}

		private void AddISupportInitialize()
		{
			if (isCSharp)
				type.BaseTypes.Add(ISupportInitializeRef);
			else
				type.BaseTypes.Add(typeof(ISupportInitialize));

			//private const string InitializationNotBegun = "Initialization has not been started.";
			CodeMemberField initializationNotBegun = new CodeMemberField(typeof(string), "InitializationNotBegun");
			initializationNotBegun.Attributes = MemberAttributes.Const;
			initializationNotBegun.InitExpression = new CodePrimitiveExpression("Initialization has not been started.");
			type.Members.Add(initializationNotBegun);

			//private const string NotInitialized = "The object has not been initialized properly.";
			CodeMemberField notInitialized = new CodeMemberField(typeof(string), "NotInitialized");
			notInitialized.Attributes = MemberAttributes.Const;
			notInitialized.InitExpression = new CodePrimitiveExpression("The object has not been initialized properly. Call Initialize prior to use.");
			type.Members.Add(notInitialized);

			//bool _beginCalled;
			type.Members.Add(new CodeMemberField(typeof(bool), "_beginCalled"));

			//bool _isInitialized;
			type.Members.Add(new CodeMemberField(typeof(bool), "_isInitialized"));

			//private bool RequiresInitialize
			//{
			//   get
			//   {
			//      return (((ISupportInitializeNotification)this).IsInitialized == false) ||
			//         ((IChangeTracking)this).IsChanged;
			//   }
			//}
			CodeMemberProperty requiresInit = new CodeMemberProperty();
			requiresInit.Name = "RequiresInitialize";
			requiresInit.Type = new CodeTypeReference(typeof(bool));
			requiresInit.Attributes = MemberAttributes.Private;
			requiresInit.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodeBinaryOperatorExpression(
						new CodeBinaryOperatorExpression(
							new CodePropertyReferenceExpression(
								new CodeCastExpression(
									ISupportInitializeNotificationRef,
									new CodeThisReferenceExpression()),
								"IsInitialized"
							),
							CodeBinaryOperatorType.ValueEquality,
							new CodePrimitiveExpression(false)
						),
						CodeBinaryOperatorType.BooleanOr,
						new CodePropertyReferenceExpression(
							new CodeCastExpression(
								IChangeTrackingRef,
								new CodeThisReferenceExpression()
							),
							"IsChanged"
						)
					)
				)
			);
			type.Members.Add(requiresInit);

			///// <summary>
			///// Validates object properties and initializes it for use.
			///// </summary>
			//public void Initialize()
			//{
			//   if (RequiresInitialize)
			//   {
			//      DoValidate();
			//      _isChanged = false;

			//      InitializeNested();

			//      DoInitialize();
			//      _isInitialized = true;

			//      if (Initialized != null)
			//      {
			//         Initialized(this, EventArgs.Empty);
			//      }
			//   }
			//}
			CodeMemberMethod initialize = new CodeMemberMethod();
			initialize.Name = "Initialize";
			initialize.Attributes = MemberAttributes.Public | MemberAttributes.Final;
			initialize.Comments.Add(new CodeCommentStatement(
				"<summary>Validates object properties and initializes it for use.</summary>", true));
			initialize.Statements.Add(
				new CodeConditionStatement(
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						"RequiresInitialize"
					),
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"DoValidate"
						)
					),
					AssignField("_isChanged", false),
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"InitializeNested"
						)
					),
					new CodeExpressionStatement(
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"DoInitialize"
						)
					),
					AssignField("_isInitialized", true),
					CallEventIfNotNull("Initialized")
				)
			);
			type.Members.Add(initialize);

			///// <summary>
			///// Initializes the nested properties that support <see cref="ISupportInitialize"/>.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected virtual void InitializeNested()
			//{
			//   ISupportInitialize initializable;

			//   initializable = (ISupportInitialize)nested;
			//   if (initializable != null)
			//   {
			//	    initializable.BeginInit();
			//     initializable.EndInit();
			//   }
			//}
			CodeMemberMethod initializeNested = new CodeMemberMethod();
			initializeNested.Name = "InitializeNested";
			initializeNested.Comments.Add(new CodeCommentStatement(
				"<summary>Initializes the nested properties that support <see cref='ISupportInitialize'/>.</summary>", true));
			initializeNested.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				initializeNested.Attributes = MemberAttributes.Private;
			else
				initializeNested.Attributes = MemberAttributes.Family;
			if (publicProperties.Count > 0)
			{
				// To avoid adding the field if it's not used, which would 
				// generate a warning.
				bool addField = false;
				CodeExpression initializable = new CodeVariableReferenceExpression("initializable");
				foreach (Property prop in publicProperties)
				{
					if (prop.ShouldCast)
					{
						addField = true;
						initializeNested.Statements.Add(
							new CodeAssignStatement(
								initializable,
								CastProperty(prop.Name, prop.ImplementsISupportInitialize, ISupportInitializeRef)
							)
						);
						initializeNested.Statements.Add(
							new CodeConditionStatement(
								new CodeBinaryOperatorExpression(
									initializable,
									CodeBinaryOperatorType.IdentityInequality,
									new CodePrimitiveExpression(null)
								),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"BeginInit")),
								new CodeExpressionStatement(new CodeMethodInvokeExpression(
									initializable,
									"EndInit"))
							)
						);
					}
				}

				if (addField)
				{
					initializeNested.Statements.Insert(0, 
						new CodeVariableDeclarationStatement(ISupportInitializeRef, "initializable"));
				}
			}
			type.Members.Add(initializeNested);

			//void ISupportInitialize.BeginInit()
			//{
			//   beginCalled = true;
			//}
			CodeMemberMethod beginInit = new CodeMemberMethod();
			beginInit.Name = "BeginInit";
			beginInit.Attributes = MemberAttributes.Private;
			AddImplementationType(beginInit, ISupportInitializeRef);
			beginInit.Statements.Add(AssignField("_beginCalled", true));
			type.Members.Add(beginInit);

			//void ISupportInitialize.EndInit()
			//{
			//   if (!beginCalled)
			//   {
			//      throw new InvalidOperationException(InitializationNotBegun);
			//   }
			//
			//   Initialize();
			//}
			CodeMemberMethod endInit = new CodeMemberMethod();
			endInit.Name = "EndInit";
			endInit.Attributes = MemberAttributes.Private;
			AddImplementationType(endInit, ISupportInitializeRef);
			endInit.Statements.Add(
				new CodeConditionStatement(
					new CodeBinaryOperatorExpression(
						new CodeFieldReferenceExpression(
							new CodeThisReferenceExpression(),
							"_beginCalled"
						),
						CodeBinaryOperatorType.ValueEquality,
						new CodePrimitiveExpression(false)
					),
					new CodeThrowExceptionStatement(
						new CodeObjectCreateExpression(
							typeof(InvalidOperationException).Name,
							new CodeFieldReferenceExpression(
								new CodeTypeReferenceExpression(type.Name),
								"InitializationNotBegun"
							)
						)
					)
				)
			);
			endInit.Statements.Add(
				new CodeMethodInvokeExpression(
					new CodeThisReferenceExpression(),
					"Initialize"
				)
			);
			type.Members.Add(endInit);
		}

		private void AddISupportInitializeNotification()
		{
			if (isCSharp)
				type.BaseTypes.Add(ISupportInitializeNotificationRef);
			else
				type.BaseTypes.Add(typeof(ISupportInitializeNotification));

			///// <summary>
			///// Signals that the object has been initialized.
			///// </summary>
			//public event EventHandler Initialized;
			CodeMemberEvent initialized = new CodeMemberEvent();
			initialized.Name = "Initialized";
			initialized.Attributes = MemberAttributes.Public;
			initialized.Type = new CodeTypeReference(typeof(EventHandler).Name);
			initialized.ImplementationTypes.Add(ISupportInitializeNotificationRef);
			initialized.Comments.Add(new CodeCommentStatement(
				"<summary>Signals that the object has been initialized.</summary>", true));
			type.Members.Add(initialized);

			//bool ISupportInitializeNotification.IsInitialized
			//{
			//   get 
			//   {
			//      return _isInitialized && AreNestedInitialized;
			//   }
			//}
			CodeMemberProperty isInitialized = new CodeMemberProperty();
			isInitialized.Name = "IsInitialized";
			isInitialized.Type = new CodeTypeReference(typeof(bool));
			isInitialized.Attributes = MemberAttributes.Private;
			AddImplementationType(isInitialized, ISupportInitializeNotificationRef);
			isInitialized.GetStatements.Add(new CodeMethodReturnStatement(
				new CodeBinaryOperatorExpression(
					new CodeFieldReferenceExpression(
						new CodeThisReferenceExpression(),
						"_isInitialized"
					),
					CodeBinaryOperatorType.BooleanAnd,
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						"AreNestedInitialized"
					)
				)
			));
			type.Members.Add(isInitialized);

			///// <summary>
			///// Gets whether the object properties that support 
			///// <see cref="ISupportInitializeNotification"/> are initialized.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected virtual bool AreNestedInitialized
			//{
			//   get
			//   {
			//      return true && IsValueInitialized(nested as ISupportInitializeNotification);
			//   }
			//}
			CodeMemberProperty nestedInitialized = new CodeMemberProperty();
			nestedInitialized.Name = "AreNestedInitialized";
			nestedInitialized.Comments.Add(new CodeCommentStatement(
				" <summary>Gets whether the object properties that support <see cref='ISupportInitializeNotification'/> are initialized.</summary>", true));
			nestedInitialized.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				nestedInitialized.Attributes = MemberAttributes.Private;
			else
				nestedInitialized.Attributes = MemberAttributes.Family;
			nestedInitialized.Type = new CodeTypeReference(typeof(bool));

			CodeExpression retExpr = new CodePrimitiveExpression(true);
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsISupportInitializeNotification,
						ISupportInitializeNotificationRef);

					retExpr = new CodeBinaryOperatorExpression(
						retExpr,
						CodeBinaryOperatorType.BooleanAnd,
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"IsValueInitialized",
							cast
						)
					);
				}
			}
			nestedInitialized.GetStatements.Add(new CodeMethodReturnStatement(retExpr));
			type.Members.Add(nestedInitialized);

			///// <summary>
			///// Determines whether the value is null or its 
			///// <see cref="ISupportInitializeNotification.IsInitialized"/> is <see langword="true" />;
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected bool IsValueInitialized(ISupportInitializeNotification child)
			//{
			//   return child == null || child.IsInitialized;
			//}
			CodeMemberMethod isValueInitialized = new CodeMemberMethod();
			isValueInitialized.Name = "IsValueInitialized";
			isValueInitialized.Comments.Add(new CodeCommentStatement(
				@"<summary>Determines whether the value is null or its 
				<see cref='ISupportInitializeNotification.IsInitialized'/> is <see langword='true' />.
				</summary>", true));
			isValueInitialized.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				isValueInitialized.Attributes = MemberAttributes.Private;
			else
				isValueInitialized.Attributes = MemberAttributes.Family | MemberAttributes.Final;
			isValueInitialized.Parameters.Add(new CodeParameterDeclarationExpression(ISupportInitializeNotificationRef, "child"));
			isValueInitialized.ReturnType = new CodeTypeReference(typeof(bool));
			isValueInitialized.Statements.Add(
				new CodeMethodReturnStatement(
					new CodeBinaryOperatorExpression(
						new CodeBinaryOperatorExpression(
							new CodeArgumentReferenceExpression("child"),
							CodeBinaryOperatorType.IdentityEquality,
							new CodePrimitiveExpression(null)
						),
						CodeBinaryOperatorType.BooleanOr,
						new CodePropertyReferenceExpression(
							new CodeArgumentReferenceExpression("child"),
							"IsInitialized"
						)
					)
				)
			);
			type.Members.Add(isValueInitialized);
		}

		private void AddIChangeTracking()
		{
			if (isCSharp)
				type.BaseTypes.Add(IChangeTrackingRef);
			else
				type.BaseTypes.Add(typeof(IChangeTracking));

			//bool _isChanged;
			type.Members.Add(new CodeMemberField(typeof(bool), "_isChanged"));

			//void IChangeTracking.AcceptChanges()
			//{
			//   Initialize();
			//}
			CodeMemberMethod acceptChanges = new CodeMemberMethod();
			acceptChanges.Name = "AcceptChanges";
			acceptChanges.Attributes = MemberAttributes.Private;
			AddImplementationType(acceptChanges, IChangeTrackingRef);
			acceptChanges.Statements.Add(
				new CodeMethodInvokeExpression(
					new CodeThisReferenceExpression(),
					"Initialize"
				)
			);
			type.Members.Add(acceptChanges);

			//bool IChangeTracking.IsChanged
			//{
			//   get
			//   {
			//      return _isChanged || HasNestedChanges;
			//   }
			//}
			CodeMemberProperty isChanged = new CodeMemberProperty();
			isChanged.Name = "IsChanged";
			isChanged.Type = new CodeTypeReference(typeof(bool));
			isChanged.Attributes = MemberAttributes.Private;
			AddImplementationType(isChanged, IChangeTrackingRef);
			isChanged.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodeBinaryOperatorExpression(
						new CodeFieldReferenceExpression(
							new CodeThisReferenceExpression(),
							"_isChanged"),
						CodeBinaryOperatorType.BooleanOr,
						new CodePropertyReferenceExpression(
							new CodeThisReferenceExpression(),
							"HasNestedChanges"
						)
					)
				)
			);
			type.Members.Add(isChanged);

			///// <summary>
			///// Gets whether the object properties that support 
			///// <see cref="IChangeTracking"/> report changes.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected virtual bool HasNestedChanges
			//{
			//   get
			//   {
			//      return false || IsValueChanged(nested as IChangeTracking);
			//   }
			//}
			CodeMemberProperty hasNestedChanges = new CodeMemberProperty();
			hasNestedChanges.Name = "HasNestedChanges";
			hasNestedChanges.Comments.Add(new CodeCommentStatement(
				"<summary>Gets whether the object properties that support <see cref='IChangeTracking'/> report changes.</summary>", true));
			hasNestedChanges.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				hasNestedChanges.Attributes = MemberAttributes.Private;
			else
				hasNestedChanges.Attributes = MemberAttributes.Family;
			hasNestedChanges.Type = new CodeTypeReference(typeof(bool));

			CodeExpression retExpr = new CodePrimitiveExpression(false);
			foreach (Property prop in publicProperties)
			{
				if (prop.ShouldCast)
				{
					CodeExpression cast = CastProperty(prop.Name,
						prop.ImplementsIChangeTracking,
						IChangeTrackingRef);

					retExpr = new CodeBinaryOperatorExpression(
						retExpr,
						CodeBinaryOperatorType.BooleanOr,
						new CodeMethodInvokeExpression(
							new CodeThisReferenceExpression(),
							"IsValueChanged",
							cast
						)
					);
				}
			}
			hasNestedChanges.GetStatements.Add(new CodeMethodReturnStatement(retExpr));
			type.Members.Add(hasNestedChanges);

			///// <summary>
			///// Determines whether the value is not null and has changed.
			///// </summary>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected bool IsValueChanged(IChangeTracking value)
			//{
			//   return value != null && value.IsChanged;
			//}
			CodeMemberMethod isValueChanged = new CodeMemberMethod();
			isValueChanged.Name = "IsValueChanged";
			isValueChanged.Comments.Add(new CodeCommentStatement(
				"<summary>Determines whether the value is not null and has changed.</summary>", true));
			isValueChanged.CustomAttributes.Add(EditorBrowsableNever);
			if (isSealed)
				isValueChanged.Attributes = MemberAttributes.Private;
			else
				isValueChanged.Attributes = MemberAttributes.Family | MemberAttributes.Final;
			isValueChanged.Parameters.Add(new CodeParameterDeclarationExpression(IChangeTrackingRef, "value"));
			isValueChanged.ReturnType = new CodeTypeReference(typeof(bool));
			isValueChanged.Statements.Add(
				new CodeMethodReturnStatement(
					new CodeBinaryOperatorExpression(
						new CodeBinaryOperatorExpression(
							new CodeArgumentReferenceExpression("value"),
							CodeBinaryOperatorType.IdentityInequality,
							new CodePrimitiveExpression(null)
						),
						CodeBinaryOperatorType.BooleanAnd,
						new CodePropertyReferenceExpression(
							new CodeArgumentReferenceExpression("value"),
							"IsChanged"
						)
					)
				)
			);
			type.Members.Add(isValueChanged);
		}

		private void AddINotifyPropertyChanged()
		{
			if (isCSharp)
				type.BaseTypes.Add(INotifyPropertyChangedRef);
			else
				type.BaseTypes.Add(typeof(INotifyPropertyChanged));

			///// <summary>
			///// Signals that a property has changed.
			///// </summary>
			// public event PropertyChangedEventHandler PropertyChanged;
			CodeMemberEvent ev = new CodeMemberEvent();
			ev.Type = new CodeTypeReference(typeof(PropertyChangedEventHandler).Name);
			ev.Name = "PropertyChanged";
			ev.Attributes = MemberAttributes.Public;
			ev.ImplementationTypes.Add(INotifyPropertyChangedRef);
			ev.Comments.Add(new CodeCommentStatement(
				"<summary>Signals that a property has changed.</summary>", true));
			type.Members.Add(ev);

			///// <summary>
			///// Raises the <see cref="PropertyChanged"/> event.
			///// </summary>
			///// <param name="property">Name of the property that changed.</param>
			//[EditorBrowsable(EditorBrowsableState.Never)]
			//protected void RaisePropertyChanged(string property)
			//{
			//   if (PropertyChanged != null)
			//      PropertyChanged(this, new PropertyChangedEventArgs(property));

			//   _isChanged = true;
			//}
			CodeMemberMethod raiser = new CodeMemberMethod();
			raiser.Name = "RaisePropertyChanged";
			raiser.Comments.Add(new CodeCommentStatement(
				@"<summary>Raises the <see cref='PropertyChanged'/> event.</summary>
				  <param name='property'>Name of the property that changed.</param>", true));
			raiser.CustomAttributes.Add(EditorBrowsableNever);
			raiser.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "property"));
			if (isSealed)
				raiser.Attributes = MemberAttributes.Private;
			else
				raiser.Attributes = MemberAttributes.Family | MemberAttributes.Final;

			CodeConditionStatement notNull = new CodeConditionStatement(
				//if (PropertyChanged != null)
				new CodeBinaryOperatorExpression(
					new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged"),
					CodeBinaryOperatorType.IdentityInequality,
					new CodePrimitiveExpression(null)),
				new CodeExpressionStatement(
				//   PropertyChanged(this, new PropertyChangedEventArgs(property));
					new CodeDelegateInvokeExpression(
						new CodeEventReferenceExpression(new CodeThisReferenceExpression(), "PropertyChanged"),
						new CodeThisReferenceExpression(),
						new CodeObjectCreateExpression(
							typeof(PropertyChangedEventArgs).Name,
							new CodeArgumentReferenceExpression("property")
						)
					)
				)
			);
			raiser.Statements.Add(notNull);
			//   _isChanged = true;
			raiser.Statements.Add(AssignField("_isChanged", true));

			type.Members.Add(raiser);
		}

		private void AddPropertyChanged(string propertyName)
		{
			string eventName = propertyName + "Changed";

			///// <summary>
			///// Signals that the property <see cref="[prop.Name]"/> has changed.
			///// </summary>
			// public event EventHandler [prop.Name]Changed;
			CodeMemberEvent ev = new CodeMemberEvent();
			ev.Type = new CodeTypeReference(typeof(EventHandler).Name);
			ev.Name = eventName;
			ev.Attributes = MemberAttributes.Public;
			ev.Comments.Add(new CodeCommentStatement(String.Format(
@"<summary>Signals that the property <see cref='{0}'/> has changed.</summary>", propertyName), true));

			type.Members.Add(ev);

			///// <summary>
			///// Raises the <see cref="[prop.Name]Changed"/> event.
			///// </summary>
			// private void Raise[prop.Name]Changed()
			CodeMemberMethod raiser = new CodeMemberMethod();
			raiser.Name = "Raise" + eventName;
			raiser.Attributes = MemberAttributes.Private;
			raiser.Comments.Add(new CodeCommentStatement(String.Format(
@"<summary>Raises the <see cref='{0}Changed'/> event.</summary>", propertyName), true));

			raiser.Statements.Add(CallEventIfNotNull(eventName));
			// RaisePropertyChanged([prop.Name]);
			raiser.Statements.Add(new CodeMethodInvokeExpression(
				new CodeThisReferenceExpression(),
				"RaisePropertyChanged",
				new CodePrimitiveExpression(propertyName)));
			type.Members.Add(raiser);
		}

		private void AddValidation()
		{
			//protected void EnsureInitialized()
			//{
			//  if (RequiresInitialize)
			//  {
			//     throw new InvalidOperationException(NotInitialized);
			//  }
			//}
			CodeMemberMethod ensureInitialized = new CodeMemberMethod();
			ensureInitialized.Name = "EnsureInitialized";
			ensureInitialized.Comments.Add(
				new CodeCommentStatement(@"<summary>
Checks that the object has been properly initialized through 
calls to <see cref='ISupportInitialize.BeginInit'/> and 
<see cref='ISupportInitialize.EndInit'/> or the <see cref='Initialize'/> method.
</summary>
<exception cref='InvalidOperationException'>The object was not initialized.</exception>",
					true));
			if (isSealed)
				ensureInitialized.Attributes = MemberAttributes.Private;
			else
				ensureInitialized.Attributes = MemberAttributes.Family | MemberAttributes.Final;

			//   if (!isInitialized)
			//   {
			//      throw new InvalidOperationException(NotInitialized);
			//   }
			ensureInitialized.Statements.Add(
				new CodeConditionStatement(
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						"RequiresInitialize"
					),
					new CodeThrowExceptionStatement(
						new CodeObjectCreateExpression(
							typeof(InvalidOperationException).Name,
							new CodeFieldReferenceExpression(
								new CodeTypeReferenceExpression(type.Name),
								"NotInitialized"
							)
						)
					)
				)
			);
			type.Members.Add(ensureInitialized);
		}

		private void AddImplementationType(CodeMemberMethod method, CodeTypeReference implementation)
		{
			if (isCSharp)
				method.PrivateImplementationType = implementation;
			else
				method.ImplementationTypes.Add(implementation);
		}

		private void AddImplementationType(CodeMemberProperty property, CodeTypeReference implementation)
		{
			if (isCSharp)
				property.PrivateImplementationType = implementation;
			else
				property.ImplementationTypes.Add(implementation);
		}

		private CodeExpression CastProperty(string name, bool directCast, CodeTypeReference targetType)
		{
			CodeExpression cast;
			if (directCast)
			{
				// Normal cast is faster, so we 
				// do a direct cast if we could determine 
				// the property type already implements 
				// the interface.
				cast = new CodeCastExpression(
					targetType,
					new CodePropertyReferenceExpression(
						new CodeThisReferenceExpression(),
						name
					)
				);
			}
			else
			{
				// "as" cast is not supported in CodeDom. 
				// Must generate a codesnippet according to language.
				if (isCSharp)
				{
					cast = new CodeSnippetExpression(name + " as " + targetType.BaseType);
				}
				else
				{
					cast = new CodeSnippetExpression("TryCast(" + name + ", " + targetType.BaseType + ")");
				}
			}
			return cast;
		}

		private static CodeStatement AssignField(string fieldName, object value)
		{
			if (value is CodeExpression)
			{
				return new CodeAssignStatement(
					new CodeFieldReferenceExpression(
						new CodeThisReferenceExpression(),
						fieldName
					),
					(CodeExpression)value
				);
			}
			else
			{
				return new CodeAssignStatement(
					new CodeFieldReferenceExpression(
						new CodeThisReferenceExpression(),
						fieldName
					),
					new CodePrimitiveExpression(value)
				);
			}
		}

		private static CodeStatement CallEventIfNotNull(string eventName)
		{
			//   if (eventName != null)
			//   {
			//      eventName(this, EventArgs.Empty);
			//   }
			CodeEventReferenceExpression ev = new CodeEventReferenceExpression(
				new CodeThisReferenceExpression(), eventName);

			CodeConditionStatement notNull = new CodeConditionStatement(
				new CodeBinaryOperatorExpression(
					ev,
					CodeBinaryOperatorType.IdentityInequality,
					new CodePrimitiveExpression(null)
				),
				new CodeExpressionStatement(
					new CodeDelegateInvokeExpression(
						ev,
						new CodeThisReferenceExpression(),
						new CodeFieldReferenceExpression(
							new CodeTypeReferenceExpression(typeof(EventArgs).Name),
							"Empty"
						)
					)
				)
			);

			return notNull;
		}
	}
}
